package com.linkoog.devtools.dubbo.toolwindow.environment;

import com.intellij.ide.IdeBundle;
import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.*;
import com.intellij.ui.table.JBTable;
import com.intellij.ui.table.TableView;
import com.intellij.util.Function;
import com.intellij.util.FunctionUtil;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.*;
import com.intellij.util.ui.table.ComboBoxTableCellEditor;
import com.intellij.util.ui.table.TableModelEditor;
import com.intellij.util.xmlb.XmlSerializer;
import com.linkoog.devtools.dubbo.persistent.DubboEnvironmentPersistent;
import com.linkoog.devtools.utils.CricleNumber;
import org.jdom.Element;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class DubboEnvironmentTableEditer extends CollectionModelEditor<DubboEnvironment, CollectionItemEditor<DubboEnvironment>> {

    private final static CricleNumber cricleNumber = new CricleNumber(50, false);

    private final TableView<DubboEnvironment> table;
    private final ToolbarDecorator toolbarDecorator;

    private final MyListTableModel model;

    public DubboEnvironmentTableEditer(){
        this(DubboEnvironmentPersistent.getInstance().getDubboEnvironments(), COLUMNS, new MyDialogItemEditor(), "No dubbo environment configured");
    }



    private DubboEnvironmentTableEditer(@NotNull ColumnInfo[] columns,
                                       @NotNull CollectionItemEditor<DubboEnvironment> itemEditor,
                                       @NotNull @Nls(capitalization = Nls.Capitalization.Sentence) String emptyText) {
        this(Collections.emptyList(), columns, itemEditor, emptyText);
    }

    /**
     * source will be copied, passed list will not be used directly
     *
     * Implement {@link DialogItemEditor} instead of {@link CollectionItemEditor} if you want provide dialog to edit.
     */
    private DubboEnvironmentTableEditer(@NotNull List<DubboEnvironment> items, ColumnInfo @NotNull [] columns, @NotNull CollectionItemEditor<DubboEnvironment> itemEditor,
                                       @NotNull @Nls(capitalization = Nls.Capitalization.Sentence) String emptyText) {
        super(itemEditor);

        model = new MyListTableModel(columns, new ArrayList<>(items));
        table = new TableView<>(model);
        table.setShowGrid(false);
        table.setDefaultEditor(Enum.class, ComboBoxTableCellEditor.INSTANCE);
        table.setEnableAntialiasing(true);
        table.setPreferredScrollableViewportSize(JBUI.size(200, -1));
        table.setVisibleRowCount(JBTable.PREFERRED_SCROLLABLE_VIEWPORT_HEIGHT_IN_ROWS);
        new TableSpeedSearch(table);
        ColumnInfo firstColumn = columns[0];
        if ((firstColumn.getColumnClass() == boolean.class || firstColumn.getColumnClass() == Boolean.class) && firstColumn.getName().isEmpty()) {
            TableUtil.setupCheckboxColumn(table.getColumnModel().getColumn(0), 0);
            JBTable.setupCheckboxShortcut(table, 0);
        }

        boolean needTableHeader = false;
        for (ColumnInfo column : columns) {
            if (!StringUtil.isEmpty(column.getName())) {
                needTableHeader = true;
                break;
            }
        }

        if (!needTableHeader) {
            table.setTableHeader(null);
        }

        table.getEmptyText().setText(emptyText);
        MyRemoveAction removeAction = new MyRemoveAction();
        toolbarDecorator = ToolbarDecorator.createDecorator(table, this).setRemoveAction(removeAction).setRemoveActionUpdater(removeAction);

        if (itemEditor instanceof DialogItemEditor) {
            addDialogActions();
        }
    }

    private void addDialogActions() {
        toolbarDecorator.setEditAction(button -> {
            DubboEnvironment item = table.getSelectedObject();
            if (item != null) {
                Function<DubboEnvironment, DubboEnvironment> mutator;
                if (helper.isMutable(item)) {
                    mutator = FunctionUtil.id();
                } else {
                    final int selectedRow = table.getSelectedRow();
                    mutator = item12 -> helper.getMutable(item12, selectedRow);
                }
                ((DialogItemEditor)itemEditor).edit(item, mutator, false);
                IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(table, true));
            }
        }).setEditActionUpdater(e -> {
            DubboEnvironment item = table.getSelectedObject();
            return item != null && ((DialogItemEditor)itemEditor).isEditable(item);
        });

        if (((DialogItemEditor)itemEditor).isUseDialogToAdd()) {
            toolbarDecorator.setAddAction(button -> {
                DubboEnvironment item = createElement();
                ((DialogItemEditor)itemEditor).edit(item, item1 -> {
                    model.addRow(item1);
                    //新增
                    final DubboEnvironmentPersistent environmentPersistent = DubboEnvironmentPersistent.getInstance();
                    environmentPersistent.getDubboEnvironments().add(item1);
                    return item1;
                }, true);
            });
        }
    }

    @NotNull
    public DubboEnvironmentTableEditer disableUpDownActions() {
        toolbarDecorator.disableUpDownActions();
        return this;
    }

    public void setShowGrid(boolean v) {
        table.setShowGrid(v);
    }

    @NotNull
    public DubboEnvironmentTableEditer enabled(boolean value) {
        table.setEnabled(value);
        return this;
    }

    public static abstract class DataChangedListener<DubboEnvironment> implements TableModelListener {
        public abstract void dataChanged(@NotNull ColumnInfo<DubboEnvironment, ?> columnInfo, int rowIndex);

        @Override
        public void tableChanged(@NotNull TableModelEvent e) {
        }
    }

    public DubboEnvironmentTableEditer modelListener(@NotNull DataChangedListener<DubboEnvironment> listener) {
        model.dataChangedListener = listener;
        model.addTableModelListener(listener);
        return this;
    }

    @NotNull
    public ListTableModel<DubboEnvironment> getModel() {
        return model;
    }

    public interface DialogItemEditor extends CollectionItemEditor<DubboEnvironment> {
        void edit(@NotNull DubboEnvironment item, @NotNull Function<? super DubboEnvironment, ? extends DubboEnvironment> mutator, boolean isAdd);

        void applyEdited(@NotNull DubboEnvironment oldItem, @NotNull DubboEnvironment newItem);

        default boolean isEditable(@NotNull DubboEnvironment item) {
            return true;
        }

        default boolean isUseDialogToAdd() {
            return false;
        }
    }

    public static <T> void cloneUsingXmlSerialization(@NotNull T oldItem, @NotNull T newItem) {
        Element serialized = com.intellij.configurationStore.XmlSerializer.serialize(oldItem);
        if (serialized != null) {
            XmlSerializer.deserializeInto(newItem, serialized);
        }
    }

    private final class MyListTableModel extends ListTableModel<DubboEnvironment> {
        private List<DubboEnvironment> items;
        private DataChangedListener<DubboEnvironment> dataChangedListener;

        MyListTableModel(ColumnInfo @NotNull [] columns, @NotNull List<DubboEnvironment> items) {
            super(columns, items);

            this.items = items;
        }

        @Override
        public void setItems(@NotNull List<DubboEnvironment> items) {
            this.items = items;
            super.setItems(items);
        }

        @Override
        public void removeRow(int index) {
            helper.remove(getItem(index));
            super.removeRow(index);
        }

        @Override
        public void setValueAt(Object newValue, int rowIndex, int columnIndex) {
            if (rowIndex < getRowCount()) {
                @SuppressWarnings("unchecked")
                ColumnInfo<DubboEnvironment, Object> column = (ColumnInfo<DubboEnvironment, Object>)getColumnInfos()[columnIndex];
                DubboEnvironment item = getItem(rowIndex);
                Object oldValue = column.valueOf(item);
                if (column.getColumnClass() == String.class
                        ? !Comparing.strEqual(((String)oldValue), ((String)newValue))
                        : !Comparing.equal(oldValue, newValue)) {

                    column.setValue(helper.getMutable(item, rowIndex), newValue);
                    if (dataChangedListener != null) {
                        dataChangedListener.dataChanged(column, rowIndex);
                    }
                }
            }
        }
    }

    public abstract static class EditableColumnInfo<Item, Aspect> extends ColumnInfo<Item, Aspect> {
        public EditableColumnInfo(@NotNull @NlsContexts.ColumnName String name) {
            super(name);
        }

        public EditableColumnInfo() {
            super("");
        }

        @Override
        public boolean isCellEditable(Item item) {
            return true;
        }
    }

    @NotNull
    public JComponent createComponent() {
        return toolbarDecorator.addExtraAction(
                new DumbAwareAction(IdeBundle.message("button.copy"),"",  PlatformIcons.COPY_ICON) {
                    @Override
                    public void actionPerformed(@NotNull AnActionEvent e) {
                        TableUtil.stopEditing(table);

                        List<DubboEnvironment> selectedItems = table.getSelectedObjects();
                        if (selectedItems.isEmpty()) {
                            return;
                        }

                        //clone复制
                        final DubboEnvironmentPersistent environmentPersistent = DubboEnvironmentPersistent.getInstance();
                        for (DubboEnvironment item : selectedItems) {
                            model.addRow(itemEditor.clone(item, false));
                            environmentPersistent.getDubboEnvironments().add(item);
                        }

                        IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(table, true));
                        TableUtil.updateScroller(table);
                    }
                    @Override
                    public @NotNull ActionUpdateThread getActionUpdateThread() {
                        return ActionUpdateThread.EDT;
                    }
                }
        ).createPanel();
    }

    @NotNull
    @Override
    protected List<DubboEnvironment> getItems() {
        return model.items;
    }

    public void selectItem(@NotNull final DubboEnvironment item) {
        table.clearSelection();

        Ref<DubboEnvironment> ref;
        if (helper.hasModifiedItems()) {
            ref = Ref.create();
            helper.process((modified, original) -> {
                if (item == original) {
                    ref.set(modified);
                }
                return ref.isNull();
            });
        }
        else {
            ref = null;
        }

        table.addSelection(ref == null || ref.isNull() ? item : ref.get());
    }

    @NotNull
    public List<DubboEnvironment> apply() {
        if (helper.hasModifiedItems()) {
            @SuppressWarnings("unchecked")
            final ColumnInfo<DubboEnvironment, Object>[] columns = model.getColumnInfos();
            helper.process((newItem, oldItem) -> {
                for (ColumnInfo<DubboEnvironment, Object> column : columns) {
                    if (column.isCellEditable(newItem)) {
                        column.setValue(oldItem, column.valueOf(newItem));
                    }
                }

                if (itemEditor instanceof DialogItemEditor) {
                    ((DialogItemEditor)itemEditor).applyEdited(oldItem, newItem);
                }

                model.items.set(ContainerUtil.indexOfIdentity(model.items, newItem), oldItem);
                return true;
            });
        }

        helper.reset(model.items);
        return model.items;
    }

    @Override
    public void reset(@NotNull List<? extends DubboEnvironment> items) {
        super.reset(items);
        model.setItems(new ArrayList<>(items));
    }

    private class MyRemoveAction implements AnActionButtonRunnable, AnActionButtonUpdater, TableUtil.ItemChecker {
        @Override
        public void run(AnActionButton button) {
            if (TableUtil.doRemoveSelectedItems(table, model, this)) {
                IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(table, true));
                TableUtil.updateScroller(table);
            }
        }

        @Override
        public boolean isOperationApplyable(@NotNull TableModel ignored, int row) {
            DubboEnvironment item = model.getItem(row);
            return item != null && itemEditor.isRemovable(item);
        }

        @Override
        public boolean isEnabled(@NotNull AnActionEvent e) {
            return areSelectedItemsRemovable(table.getSelectionModel());
        }
    }

    private static class MyDialogItemEditor implements TableModelEditor.DialogItemEditor<DubboEnvironment> {

        public MyDialogItemEditor() {
        }

        @NotNull
        @Override
        public Class<DubboEnvironment> getItemClass() {
            return DubboEnvironment.class;
        }


        @Override
        public DubboEnvironment clone(@NotNull DubboEnvironment item, boolean forInPlaceEditing) {
            DubboEnvironment dubboEnvironment = new DubboEnvironment();
            dubboEnvironment.setName(item.getName() + cricleNumber.nextNum());
            dubboEnvironment.setProtocol(item.getProtocol());
            dubboEnvironment.setIp(item.getIp());
            dubboEnvironment.setPort(item.getPort());
            dubboEnvironment.setVersion(item.getVersion());
            dubboEnvironment.setGroup(item.getGroup());
            return dubboEnvironment;
        }

        //对应工具栏编辑按钮事件
        @Override
        public void edit(@NotNull DubboEnvironment item, @NotNull Function<? super DubboEnvironment, ? extends DubboEnvironment> mutator, boolean isAdd) {
            DubboEnvironmentAddDialog dialog = new DubboEnvironmentAddDialog(item);
            dialog.setCallback(dubboEnvironment -> {
                if (dubboEnvironment != null) {
                    DubboEnvironment fun = mutator.fun(item);
                    fun.setName(dubboEnvironment.getName());
                    fun.setProtocol(dubboEnvironment.getProtocol());
                    fun.setIp(dubboEnvironment.getIp());
                    fun.setPort(dubboEnvironment.getPort());
                    fun.setVersion(dubboEnvironment.getVersion());
                    fun.setGroup(dubboEnvironment.getGroup());
                }
            });
            dialog.show();
        }

        @Override
        public void applyEdited(@NotNull DubboEnvironment oldItem, @NotNull DubboEnvironment newItem) {
            System.out.println("=====================applyEdited=====================");
        }



        @Override
        public boolean isUseDialogToAdd() {
            return true;
        }


    }

    private static final ColumnInfo[] COLUMNS = {
            new TableModelEditor.EditableColumnInfo<DubboEnvironment, String>("name") {
                @Override
                public String valueOf(DubboEnvironment item) {
                    return item.getName();
                }

                @Override
                public void setValue(DubboEnvironment item, String value) {
                    item.setName(value);
                }

                @Override
                public boolean isCellEditable(DubboEnvironment item) {
                    return false;
                }
            },
            new TableModelEditor.EditableColumnInfo<DubboEnvironment, String>("Address") {
                @Override
                public String valueOf(DubboEnvironment item) {
                    return item.getProtocol() + "://" + item.getIp() + ":" + item.getPort();
                }

                @Override
                public boolean isCellEditable(DubboEnvironment item) {
                    return false;
                }
            },
            new TableModelEditor.EditableColumnInfo<DubboEnvironment, String>("Version") {
                @Override
                public String valueOf(DubboEnvironment item) {
                    return item.getVersion();
                }

                @Override
                public void setValue(DubboEnvironment item, String value) {
                    item.setVersion(value);
                }

                @Override
                public boolean isCellEditable(DubboEnvironment item) {
                    return false;
                }
            },
            new TableModelEditor.EditableColumnInfo<DubboEnvironment, String>("Group") {
                @Override
                public String valueOf(DubboEnvironment item) {
                    return item.getGroup();
                }

                @Override
                public void setValue(DubboEnvironment item, String value) {
                    item.setGroup(value);
                }

                @Override
                public boolean isCellEditable(DubboEnvironment item) {
                    return false;
                }
            }};

}